/** This class represents an instant messenger client 
 *  @author Copyright (c) 2004-2008 by chrmac@gmx.de */

package info.chrm.jtalker;

import java.io.*;
import java.net.*;
import javax.net.ssl.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
//import java.util.Comparator;
import info.chrm.helper.*;


/** JTalker Client.
 *  @author ChrM */
public class JTalkerClient implements Comparator {
    final public static String RELEASE = "0.7.1";
    
    /** Constructor */
    public JTalkerClient () {
       this.configFile = System.getProperty("user.home") + File.separator + ".jtalker.conf";
       this.pluginDir = System.getProperty("user.home") + File.separator;
       this.isConnected = false;
              
       if(!loadConfigFile()) {
	  // Fallback
	  javax.swing.JOptionPane.showMessageDialog(null, 
	    "You are using JTalker for the first time.\nPlease check the configuration!", 
	    "Config file not found (" + this.configFile + ") !", 
	    javax.swing.JOptionPane.INFORMATION_MESSAGE);
	 
	  this.portNum = 4444;
	  this.serverName = "localhost";
	  this.useSSL = false;
	  this.userName = System.getProperty("user.name");
	  this.userStatus = "normal";
	  this.userPasswd = "";
          this.savePassword = false;
	  this.defaultBrowser = "firefox";
	  this.defaultMailer = "kmail";
	  this.guiPlaf = UIManager.getLookAndFeel().getClass().getName();
	  this.guiTextColor = JTalkerClient.getColorAsString(java.awt.SystemColor.textText);
	  this.guiBgColor = JTalkerClient.getColorAsString(java.awt.SystemColor.controlHighlight);
	  this.newMailNotificationType = 2; // 0:none 1:toFront 2:notificationWindow 3:penetrant beep
          this.pluginDir = System.getProperty("user.home") + File.separator;
          this.pluginShortcuts = "";
       }
       
       this.myProtocol = new JTalkerClientProtocol(this);
    }

    /** Load the config file */
   private boolean loadConfigFile() {
        try {
            /*java.io.File myConfig = new java.io.File(this.configFile);
            java.io.FileReader fr = new java.io.FileReader(myConfig);
            java.io.BufferedReader in = new java.io.BufferedReader(fr);
            this.serverName = in.readLine();
            this.portNum = Integer.parseInt(in.readLine());
            this.defaultBrowser = in.readLine();
            this.defaultMailer = in.readLine();
            this.userName = in.readLine();            
            String passwd = in.readLine();
            if(passwd != null)
                this.userPasswd = passwd;            
            in.close();
            fr.close();*/

           java.io.File myConfig = new java.io.File(this.configFile);
	   JTalkerParser jtp = new JTalkerParser();
	   if(jtp.parsing(myConfig)) {
            this.serverName = jtp.getClientSettings().server_ip;
            this.portNum = Integer.parseInt(jtp.getClientSettings().server_port);
            this.useSSL = Boolean.valueOf(jtp.getClientSettings().server_ssl).booleanValue();
            this.defaultBrowser = jtp.getClientSettings().apps_browser;
            this.defaultMailer = jtp.getClientSettings().apps_mail;
            this.userName = jtp.getClientSettings().account_name;

            this.userPasswd = jtp.getClientSettings().account_passwd;
            if(this.userPasswd == null)
                this.savePassword = false;
            
	    this.guiPlaf = jtp.getClientSettings().look_plaf;
	    if(this.guiPlaf == null)
	       this.guiPlaf = UIManager.getLookAndFeel().getClass().getName();
	    this.guiTextColor = jtp.getClientSettings().look_textcolor;            
	    if(this.guiTextColor == null)
	       this.guiTextColor = JTalkerClient.getColorAsString(java.awt.SystemColor.textText);
	    this.guiBgColor = jtp.getClientSettings().look_bgcolor;            
	    if(this.guiBgColor == null)
	       this.guiBgColor = JTalkerClient.getColorAsString(java.awt.SystemColor.controlHighlight);
	    try {
	       this.newMailNotificationType = Integer.parseInt(jtp.getClientSettings().notif_userlist);
	    } catch (Exception e) {
	       this.newMailNotificationType = 2;
	    } 
            this.pluginDir = jtp.getClientSettings().plugins_dir;
            this.pluginShortcuts = jtp.getClientSettings().plugins_shortcuts;
	   } else
	      return false;     
	   
	} catch (Exception _e) {
            System.err.println("*** ERROR: " + _e.toString());
	    return false;
        }
	
	return true;
    }

    /** Helper method for converting an Color-object to a string like "#rrggbb" 
     *  @param _color The Color object that shoud be converted 
     *  @return A String like "#rrggbb" or null. */
    public static String getColorAsString(java.awt.Color _color) {
       if(_color != null) {
	  String retVal = "#";
	  String hexNum = "";
	  hexNum = Integer.toHexString(_color.getRed());
	  if(hexNum.length() == 1) hexNum = ("0" + hexNum);
	  retVal += hexNum;
	  hexNum = Integer.toHexString(_color.getGreen());
	  if(hexNum.length() == 1) hexNum = ("0" + hexNum);
	  retVal += hexNum;
	  hexNum = Integer.toHexString(_color.getBlue());
	  if(hexNum.length() == 1) hexNum = ("0" + hexNum);
	  retVal += hexNum;
	  return retVal;
       }
       
       return null;
    }


    /** Writes out the settings */
    public boolean saveSettings () {
       try {	       
	       java.io.File myConfig = new java.io.File(this.configFile);
	       java.io.FileWriter fw = new java.io.FileWriter(myConfig);
	       
	       String tmpConfig = "<" + JTalkerParser.CONFIG_ROOT + ">"
			 + "\n  <" + JTalkerParser.CONFIG_SERVER 
			    + " " + JTalkerParser.CONFIG_SERVER_IP + "=\"" + this.serverName + "\"" 
			    + " " + JTalkerParser.CONFIG_SERVER_SSL + "=\"" + this.useSSL + "\"" 
			    + " " + JTalkerParser.CONFIG_SERVER_PORT + "=\"" + this.portNum + "\" />" 
			 + "\n  <" + JTalkerParser.CONFIG_ACCOUNT 
			    + " " + JTalkerParser.CONFIG_ACCOUNT_NAME + "=\"" + this.userName + "\"";
               if(this.savePassword)
	                 tmpConfig += " " + JTalkerParser.CONFIG_ACCOUNT_PASSWD + "=\"" + this.userPasswd + "\" />";
               else
                         tmpConfig += " />";
               
	       tmpConfig += ("\n  <" + JTalkerParser.CONFIG_APPS 
			    + " " + JTalkerParser.CONFIG_APPS_BROWSER + "=\"" + this.defaultBrowser + "\"" 
			    + " " + JTalkerParser.CONFIG_APPS_MAIL + "=\"" + this.defaultMailer + "\" />" 
			 + "\n  <" + JTalkerParser.CONFIG_LOOK
			    + " " + JTalkerParser.CONFIG_LOOK_PLAF + "=\"" + this.guiPlaf + "\"" 
			    + " " + JTalkerParser.CONFIG_LOOK_TEXTCOLOR + "=\"" + this.guiTextColor + "\"" 
			    + " " + JTalkerParser.CONFIG_LOOK_BGCOLOR + "=\"" + this.guiBgColor + "\" />"
			 + "\n  <" + JTalkerParser.CONFIG_NOTIFICATION 
			    + " " + JTalkerParser.CONFIG_NOTIFICATION_USERLIST + "=\"" + this.newMailNotificationType + "\" />" 
			 + "\n  <" + JTalkerParser.CONFIG_PLUGINS 
			    + " " + JTalkerParser.CONFIG_PLUGINS_DIR + "=\"" + this.pluginDir + "\"" 
			    + " " + JTalkerParser.CONFIG_PLUGINS_SHORTCUT + "=\"" + this.pluginShortcuts + "\" />" 
			 + "\n</" + JTalkerParser.CONFIG_ROOT + ">");
               fw.write(tmpConfig);
	       fw.close();
	       return true;
	   } catch (Exception _e) {
	       System.err.println("*** ERROR: " + _e.toString());
	       return false;
	   } 
    }

    /** Sets servers properties (Port and Adress) */
    public void setServerProps(int _portNum, String _serverName) {
       this.portNum = _portNum;
       this.serverName = _serverName;
    } 

    /** Gets server data */
    public String getServerInfo() {
       return this.serverName + ":" + this.portNum;
    }

    /** Gets a member by short name */
    public MemberData getMemberByName(String _name) {
        for( int i = 0; i < this.allMembers.size(); i++ ) { 
	    if(((MemberData)this.allMembers.get(i)).name.equals(_name)) {
	        // Found User
	        return (MemberData)this.allMembers.get(i);
	    } 
        }

        return null;
    }

    /** Adds a new member or a new message to the List */
    public boolean addMember(String _name, 
			     String _status,
			     String _user_type,
			     String _message,
			     String _ip,
			     String _subject,
			     String _type) {
       
       for( int i = 0; i < this.allMembers.size(); i++ ) { 
	 if(((MemberData)this.allMembers.get(i)).short_name.equals(_name)) {
	    // Found User -> add new Message
	    if(_message != null)
	       ((MemberData)this.allMembers.get(i)).addMessage(_message, _subject, _type);
	    if(_status != null)
	       ((MemberData)this.allMembers.get(i)).status = _status;
	    return true;
	    // TODO: Check if the ip has changed!
	 } 
       }   

       // New User -> add to Member List
       MemberData newMember = new MemberData(_name, _ip, _status, _user_type);
       if(_message != null)
          newMember.addMessage(_message, _subject, _type);
       
       this.allMembers.add(newMember);

       // Sort List
       java.util.Collections.sort((List)this.allMembers, (Comparator)this);

       return true;
    }

    ///////////////// DEBUG MAIN LOOP //////////////////////////////
    public void mainLoop() {
       if(!this.connect())
	  return;
       this.loginOnServer();
       // this.createNewAccount("NewUser", "myPass");
       this.sendMessage("Hallo Du!", "MrXX");
       this.lookUp();
       this.closeConnection();
    }
    ////////////////////////////////////////////////////////////////

    /** Starts the connection */
    public boolean connect() {
       System.out.println(">>> Open TCP/IP connection to: " + this.serverName + ":" + this.portNum);
       try {	  
	    if(this.useSSL) {
	       // SSL
	       /*this.kkSocket = 
		  SSLSocketFactory.getDefault().createSocket(this.serverName, this.portNum);*/

	       // TODO: ignore all SSL-actions (Cert verification) or not
	       this.trustAllCerts();

	       System.out.println("+ Try to create a SSL-Socket...");
	       SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
	       this.kkSocket = (SSLSocket)factory.createSocket(this.serverName, this.portNum);
        
	       // Connect to the server
	       System.out.println("+ Start SSL handshake...");
	       ((SSLSocket)this.kkSocket).startHandshake();
    
	       // Retrieve the server's certificate chain
	       java.security.cert.Certificate[] serverCerts =
		  ((SSLSocket)this.kkSocket).getSession().getPeerCertificates();
	    }
	    else {
	       // NO SSL
	       System.out.println("+ Try to create a Socket...");
	       this.kkSocket = new Socket(this.serverName, this.portNum);
	    }

	    // Set Timeout to 8 sec.
	    this.kkSocket.setSoTimeout(10000);
	    
            this.out = new PrintWriter(this.kkSocket.getOutputStream(), true);
            this.in = new BufferedReader(new InputStreamReader(this.kkSocket.getInputStream()));
        } catch (UnknownHostException _e) {
            System.err.println("*** ERROR: Unknown host: " + this.serverName);
	    this.showErrorMessage(_e.toString());
            return false;
        } catch (IOException _e) {
            System.err.println("*** ERROR: Couldn't get I/O for the connection to: " + 
		  this.serverName + " (" + _e.toString() + ")");
	    this.showErrorMessage(_e.toString());
            return false;
        /*} catch (java.security.cert.CertificateEncodingException _e) {
	    System.err.println("*** ERROR: Wrong Certificate: " + _e.toString());
	    this.showErrorMessage(_e.toString());
            return false;*/
	} catch (Exception _e) {
	    System.err.println("*** ERROR: " + _e.toString());
	    this.showErrorMessage(_e.toString());
            return false;
	}

        String fromServer; // For servers reply

	// Get servers first answer after connect
	try { 
	   fromServer = this.in.readLine();
	   System.out.println("[recv]: " + fromServer);

	   
	   if(!this.saveServersReply(fromServer))
	        return false;
           else {
                // Init server listener
                // set timeout to infinite!
                this.kkSocket.setSoTimeout(0);
                this.jct = new JTalkerClientThread(this, this.in);
                this.jct.start();
           }
			 
	} catch (Exception _e) {
	   System.err.println("*** Can't read servers reply! " + _e.toString());	   
	   this.showErrorMessage(_e.toString());
	   return false;
	} 

	this.isConnected = true;
	return true;
    }

    /** Occures, if the main loop thread dies */
    public void mainLoopError() {
	if(this.isConnected) {
                javax.swing.JOptionPane.showMessageDialog(null, 
							 "Server connection error! Connect again.", 
							 "Big problem", 
							 javax.swing.JOptionPane.ERROR_MESSAGE);
                this.closeConnection();
        } 
    }

    /** Shows a error box */
    private void showErrorMessage(String _text) {
       javax.swing.JOptionPane.showMessageDialog(null, 
				 "Connection Error:\n" + _text, 
				 "Connection Problems", 
				 javax.swing.JOptionPane.ERROR_MESSAGE);
    }

    //----------------------- Main Interfaces --------------------------------
    /** Send a message */
    public boolean sendMessage(String _message, String _receiver) {
       System.out.println(">>> Send Message...");
       return this.myProtocol.sendMessage(_message, _receiver, "normal", null, null);
    }
    
    /** Send a message of type "_type" */
    public boolean sendMessage(String _message, String _receiver, String _type, String _subject) {
       System.out.println(">>> Send Message... Type:" + _type);
       return this.myProtocol.sendMessage(_message, _receiver, _type, _subject, null);
    }
    
    /** Send a message of type "_type" and "_id" */
    public boolean sendMessage(String _message, String _receiver, String _type, String _subject, String _id) {
       System.out.println(">>> Send Message... Type:" + _type + " ID:" + _id);
       return this.myProtocol.sendMessage(_message, _receiver, _type, _subject, _id);
    }

    /** Looks up to server for new messages or current user List */
    public boolean lookUp() {
       System.out.println(">>> Lookup for Messages...");
       return this.myProtocol.sendUserStatus(false);
    }

    /** Logins user */
    public boolean loginOnServer() {
       this.STATE = STATE_LOGGING;
       System.out.println(">>> Login on server...");
       return this.myProtocol.sendUserStatus(true);
    }

    /** Creates a new Account on the server */
    public boolean createNewAccount(String _name, String _passwd) {
       boolean retVal = false;
       System.out.println(">>> Try to create a new account...");
       
       // Close Connection first - if connected
       if(this.isConnected)
	  this.closeConnection();
       
       if(this.connect()) {
	 try {
            this.STATE = STATE_ACCOUNT_CREATION;
	    retVal = this.myProtocol.sendNewAccount(_name, _passwd);
	    
            // TODO: reimplement it using MESSAGE type="register"
	    return retVal;

         } catch (Exception _e) { 
	    System.err.println("*** ERROR: Can't create accout: " + _e.toString());
	    return false;
	 } 
       }
       else
	 return false;
    }

    /** Closes the connection */
    public void closeConnection() {
       System.out.println(">>> Close connection");
       this.STATE = STATE_OFFLINE;
       this.jct.terminate();
       this.removeAllConnectionObjects();
       this.allMembers.clear(); 
       this.memberList.propertyChanged();
       
       try { 
	this.out.close();
	this.in.close();
	this.isConnected = false;
	this.kkSocket.close();

        // Inform all threads about disconnect
        this.setLastCommandResponse("disconnect01", null, null);
      } catch (Exception _e) { 
	 System.err.println("*** Can't close this connection: " + _e.toString());
      } 
    }
    //-------------------------------------------------------------------------

    /** Sends a stream to the servers */
    public boolean sendToServer(String _stream) {
       // send XML stream
       try {
           if (_stream != null) {
             System.out.println("[send]: " + _stream);
             this.out.println(_stream);
             return true;
           } else
               return false;
       } catch (Exception _e) {
           return false;
       } 
       
       // get answer
       /*try {
	  //String fromServer = this.in.readLine();
	  String fromServer = this.jct.getLastPackage();

	  if(fromServer == null)
	     return null;   
	  
	  System.out.println("[recv]: " + fromServer);
       
	  JTalkerParser myParser = new JTalkerParser();
	  if(myParser.parsing(fromServer))
	     return myParser;
	  else return null;
	 
       } catch (Exception _e) { 
	  System.err.println("*** Exception while reading from socket: " + _e.getMessage());
	  return null;
       } */
    }

    /** Parse and save servers reply */
    private boolean saveServersReply(String _serversReply) {
       try { 
          this.myProtocol.processServerPackage(_serversReply);

	  /*JTalkerParser tmpParser = new JTalkerParser();
	  if(!tmpParser.parsing(_serversReply))
	     return false;
       
	  for( int i = 0; i < tmpParser.getMemberNum(); i++ ) { 
	    this.addMember(tmpParser.getAMember(i).name,
			  tmpParser.getAMember(i).status,
			  tmpParser.getAMember(i).type,
			  null,
			  tmpParser.getAMember(i).ip,
			  null,
			  null);
	  }
	  // save messages
	  for( int i = 0; i < tmpParser.getMessagesNum(); i++ ) { 
	    this.addMember(tmpParser.getAMessage(i).sender,
			  tmpParser.getAMessage(i).senderStatus,
			  null,
			  tmpParser.getAMessage(i).content,
			  null,
			  tmpParser.getAMessage(i).messageSubject,
  			  tmpParser.getAMessage(i).messageType);
	  }	  

	  this.cleanUpList(tmpParser);*/
       
      } catch (Exception e) { 
	 System.err.println("*** Can't save servers reply! " + e.toString());
	 return false;
      } 

       return true;
    }

    /** Removes all connections on disconnect and closes all opened chat windows */
    public void removeAllConnectionObjects() {
       for( int i = 0; i < this.allMembers.size(); i++ ) { 
	  ((MemberData)this.allMembers.get(i)).destroyChatWindow();
       }
	
    }
    
    /** Clean ups list */
    synchronized public void cleanUpList(JTalkerParser _tmpParser) {
	  // check if some members are going offline
	  // TODO: Use IDs to check changes!!!
	  
	  //if(_tmpParser.getMemberNum() != this.allMembers.size()) {
	  if(true) {
	     //System.out.println("+ Num of members changed - update list! " + _tmpParser.getMemberNum() + "/" + this.allMembers.size());
	     // get local members
	     boolean userConnected = false;
	     for( int i = 0; i < this.allMembers.size(); i++ ) { 
		userConnected = false;
		MemberData tmpMember = (MemberData)this.allMembers.get(i);
		
		// get servers members
		for( int j = 0; j < _tmpParser.getMemberNum(); j++ ) { 
		   if(tmpMember.short_name.equals(_tmpParser.getAMember(j).name)) {
		      if(!tmpMember.isConnected())
			tmpMember.setConnected();		   
		      userConnected = true;
		   }
		}

		// set this user as disconnected and remove local member of no unread messages inside
		if(!userConnected) {
		   if(tmpMember.isConnected())
		     tmpMember.setDisconnected();		   
		   if(!tmpMember.hasNewMessage && !tmpMember.isChatWindowOpen()) {
		     // Remove user finally - only if no messages and no open chats
		     this.allMembers.remove(i);
		   }
		}
	     }
	  }
    }       

    /** Trust all SSL certs */
    private boolean trustAllCerts() {
       // Create a trust manager that does not validate certificate chains
       TrustManager[] trustAllCerts = new TrustManager[]{
	   new X509TrustManager() {
	       public java.security.cert.X509Certificate[] getAcceptedIssuers() {
		   return null;
	       }
	       public void checkClientTrusted(
		   java.security.cert.X509Certificate[] certs, String authType) {
	       }
	       public void checkServerTrusted(
		   java.security.cert.X509Certificate[] certs, String authType) {
	       }
	   }
       };
    
       // Install the all-trusting trust manager
       try {
	   SSLContext sc = SSLContext.getInstance("SSL");
	   sc.init(null, trustAllCerts, new java.security.SecureRandom());
	   HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
       } catch (Exception e) {
	  return false;
       }       
       return true;
    }

    /** Gets the last command response */
    //public String getLastCommandResponse() {
        /*try {
            int timeout = 50;
            while(this.lastCommandResponse == null && timeout > 0) {
                Thread.sleep(50);
                timeout--;
            }
        } catch (Exception _e) { 
            System.err.println("*** Can't get command response: " + _e.toString());
        } */

    //    return this.lastCommandResponse;
    //}

    /** Sets the last command response */
    public void setLastCommandResponse(String _id, String _from, String _content) {
        this.lastCommandResponse = _content; // Only for backward compat.

        // Dispatch the response to all registered objects
        for( int i = 0; i < this.allCommandResponseObjects.size(); i++ ) {
            ((JTalkerClientCommandInterface)this.allCommandResponseObjects.get(i)).setCommandAnswer(_id, _from, _content);
        }
    }

    /** Register an object for recieving command responses */
    public void registerForCommandResponse(JTalkerClientCommandInterface _object) {
        for( int i = 0; i < this.allCommandResponseObjects.size(); i++ ) {
            if((JTalkerClientCommandInterface)this.allCommandResponseObjects.get(i) == _object) {
                System.out.println("*** registerForCommandResponse: object already registered: " + _object.toString());
                return;
            }
        }

        this.allCommandResponseObjects.add(_object);
    }

    /** Deregister an object from command responses */
    public void deRegisterForCommandResponse(JTalkerClientCommandInterface _object) {
        System.out.println("*** deRegisterForCommandResponse: removing object: " + _object.getClass().getName());
        if(this.allCommandResponseObjects.remove(_object))
            System.out.println("*** deRegisterForCommandResponse: object removed: " + _object.getClass().getName());
    }

    // Some Plugin shortcut methods
    // All this is frontend stuff and should be moved
    // to the frontend classes later
    /** Add a plugin to the shortcut list */
    public boolean addPluginShortcut(String _name) {
        if(!this.isPluginShortcut(_name)) {
                this.pluginShortcuts += (_name + ",");
                return true;
        }

        return false;
    }

    /** Removes a plugin shortcut */
    public boolean removePluginShortcut(String _name) {
        String [] allEmbPlugins = this.pluginShortcuts.split(",");
        this.pluginShortcuts = "";
        boolean retVal = false;

        for( int i = 0; i < allEmbPlugins.length; i++ ) {
            if(!(allEmbPlugins[i].trim()).equals(_name)) {
                this.addPluginShortcut(allEmbPlugins[i].trim());
                retVal = false;
            }
        }

        return retVal;
    }

    /** Checks if a plugin shortcut exists for the given name */
    public boolean isPluginShortcut(String _name) {
        String [] allEmbPlugins = this.pluginShortcuts.split(",");

        for( int i = 0; i < allEmbPlugins.length; i++ ) {
            if((allEmbPlugins[i].trim()).equals(_name))
                return true;
        }

        return false;
    }

    /** Gets an plugin shortcut array */
    public String [] getPluginShortcuts() {
        String [] allEmbPlugins = this.pluginShortcuts.split(",");
        return allEmbPlugins;
    }

    /** Gets the frontend */
    public JTalkerClientFront getFrontend() {
        return this.myFrontend;
    }

    /** Sets the frontend */
    public void setFrontend(JTalkerClientFront jtcf) {
        if(this.myFrontend == null)
            this.myFrontend = jtcf;
    }

    // States
    public static final int STATE_OFFLINE = 0;
    public static final int STATE_LOGGING = 1;
    public static final int STATE_CONNECTED = 2;
    public static final int STATE_ACCOUNT_CREATION = 3;
    public int STATE = STATE_OFFLINE;

    private Socket kkSocket = null;
    private PrintWriter out = null;
    private BufferedReader in = null;
    public JTalkerClientProtocol myProtocol = null;
    private JTalkerClientThread jct = null;

    // Members for the Frontend
    private JTalkerClientFront myFrontend = null;
    public boolean isConnected = false;
    public int portNum = 4444;
    public String serverName = "localhost";
    public boolean useSSL = false;
    public String userName;
    public String userStatus;
    public String userPasswd;
    public boolean savePassword;
    public String defaultBrowser;
    public String defaultMailer;
    public String guiPlaf;
    public String guiTextColor;
    public String guiBgColor;
    public int newMailNotificationType = 2; // 0: none 1:toFront 2:notification window 3:penetrant beep
    public String configFile;
    public String pluginDir;
    private String pluginShortcuts; // ","-separated list of plugins, which should be embedded in the main window
    public ArrayList allMembers = new ArrayList(); // A List of MemberData Objects
    public ArrayList allCommandResponseObjects = new ArrayList(); // A List of MemberData Objects
    public MemberList memberList = new MemberList();
    private String lastCommandResponse = null;
    

    /** static main-methode */
    public static void main(String[] args) throws IOException {
        JTalkerClient theClient = new JTalkerClient();
	theClient.mainLoop();        
    }

    // Comparator methods
    public int compare(Object _o1, Object _o2) {
       return -1 * ((MemberData)_o1).type.compareToIgnoreCase(((MemberData)_o2).type);		
    }
    public boolean equals(Object _obj){
       // Not needed yet
       return true;
    }



    /** Model for the JList with all users */
    public class MemberList implements javax.swing.ListModel {      
       private ListDataListener myDataListener = null;
       
       public MemberList() {}   

       // Notify a Change
       public void propertyChanged() {
	  this.myDataListener.contentsChanged(new ListDataEvent(this, 
							       ListDataEvent.CONTENTS_CHANGED, 
							       allMembers.size()-1, 
							       0));
       }

       // List Model Listener
       public void addListDataListener(ListDataListener _l) {
	  this.myDataListener = _l;
       }
       public Object getElementAt(int _index) {
	  return ((MemberData)allMembers.get(_index));
       }
       public int getSize() {
	  return allMembers.size();
       }
       public void removeListDataListener(ListDataListener _l) {}    
    }

    /** Inner class with informations about all users and theirs messages */
    public class MemberData {
       public MemberData(String _name, String _ip, String _status, String _type) {
	  this.name = _name + " AT " + _ip;  // Backward compatibility :-(
	  this.status = _status;
	  //this.short_name = _name.substring(0, _name.lastIndexOf(" AT "));
	  this.short_name = _name;
	  this.ip = _ip;
	  this.type = _type;
	  if(this.type == null)
	     this.type = JTalkerParser.USER_TYPE_PERSISTENT;
       } 

       public void clearMessages() {
	  this.messageList.clear();
       }

       /** Sets User Status to Disconnected */
       public void setDisconnected() { 
	  this.is_connected = false;
	  // Save last state of unreaded messages
	  boolean tmpState = this.hasNewMessage;
	  
	  this.addMessage("<table border=\"0\" width=\"100%\"><tr><td bgcolor=\"#ff0000\">" + this.DISCONNECT_MESSAGE + "</td></tr></table>", 
			  "User is offline",
			  "normal");
	  
	  
	  // Don't notificate this message if chat window is not open
	  if(!this.isChatWindowOpen())
	    this.hasNewMessage = tmpState;
       }

       /** Sets User Status to Connected (again) */
       public void setConnected() { 
	  this.is_connected = true;
	  // Save last state of unreaded messages
	  boolean tmpState = this.hasNewMessage;
	  
	  this.addMessage("<table border=\"0\" width=\"100%\"><tr><td bgcolor=\"#00ff00\">" + this.CONNECT_MESSAGE + "</td></tr></table>", 
			  "User is online",
			  "normal");
	    
	  // Don't notificate this message if chat window is not open
	  if(!this.isChatWindowOpen())
	    this.hasNewMessage = tmpState;
       }

       /** Gets User Connection-Status */
       public boolean isConnected() {
	  return this.is_connected;
       }

       public void addMessage(String _content, String _subject, String _type) {
	  if(_content == null)
	     return;
	  this.hasNewMessage = true;	     
	  
	  this.messageList.add(new Message(_content, new DateMan().getTime(2), _subject, _type));
	  if(this.messageList.size() > 30)
	     this.messageList.remove(0);

          // DEBUG
          //if(this.isChatWindowOpen())
          //    this.chat_window.updateWindow();
       }

       /** Gets a message (text only messages) */
       public synchronized String getMessage(int _index) {
	  try {
	    Message tmpMsg = (Message)this.messageList.get(_index);
	    if(tmpMsg.type.equals("normal")) {
               //this.hasNewMessage = false;	     
	       return tmpMsg.content;
            } else
	       return null;
	  } catch (Exception _e) {
	     System.err.println("*** No Messages from this User: " + this.name);
	     return null;
	  } 
       }

       /** Shows a save as Dialog for cached files */
       public synchronized boolean saveBASE64Files() {
	  // Searching for files
	  for( int i = 0; i < this.numOfMessages(); i++ ) {
	     Message tmpMsg = (Message)this.messageList.get(i);
	     if(tmpMsg.type.equals("file")) {
                //this.hasNewMessage = false;	     
		BASE64Handler b64h = new BASE64Handler(this.chat_window);
		if(!b64h.saveDecodedData(tmpMsg.content, 
				         tmpMsg.subject))
		   System.out.println("*** Can't decode file: " + b64h.getLastError());
		this.messageList.remove(i);
		return true;
	     }
	  }

	  // No Files found
	  return false;
       }

       public String getDate(int _index) {
	  try { 
	    return ((Message)this.messageList.get(_index)).date;
	  } catch (Exception _e) {
	     System.err.println("*** No Messages from this User: " + this.name);
	     return null;
	  } 
       }

       /** Gets number of Messages */
       public int numOfMessages() {
	    return this.messageList.size();
       }

       /** Opens the chat window for this user */
       public void showChatWindow(JTalkerClientFront _gui_parent) {
	  if(this.chat_window == null)
	     this.chat_window = new JTalkerFrontChat(_gui_parent, this);
	     
	  this.chat_window.setVisible(true);
	  this.chat_window.toFront();
	  this.chat_window.updateWindow();
       }

       /** Checks if the chat window is open */
       public boolean isChatWindowOpen() {
	  if(this.chat_window == null)
	     return false;

	  return this.chat_window.isVisible();
       }

       /** Destroys the chat window */
       public void destroyChatWindow() {
	  if(this.chat_window != null)
	     this.chat_window.dispose();	    
       }
       
       // Private members
       private final static String DISCONNECT_MESSAGE = "Server: User is OFFLINE!";
       private final static String CONNECT_MESSAGE = "Server: User is ONLINE!";
       public String name;	 // Member name
       public String ip;	 // IP adress
       public String type;	 // userType
       public String short_name; // Member name without IP
       public String status;	 // Member status
       private ArrayList messageList = new ArrayList();	 // List of messages
       public boolean hasNewMessage = false;		 // Is there an unreaded message?
       private boolean is_connected = true;

       // Only for a swing based client
       private JTalkerFrontChat chat_window = null;

       public class Message {
	  public Message(String _content, String _date, String _subject, String _type) {
	     this.content = _content;
	     this.date = _date;
	     this.subject = _subject;
	     this.type = _type;
	     if(_type == null)
		this.type = "normal";
	  }
	  public String content;
	  public String date;
	  public String subject;
	  public String type;
       }
    }
}
